import Vector3 from "./Vector3";

export default class SphericalHarmonics3 {
	coefficients: Array<Vector3>;
	constructor() {
		this.coefficients = [];

		for (let i = 0; i < 9; i++) {
			this.coefficients.push(new Vector3());
		}
	}

	set(coefficients) {
		for (let i = 0; i < 9; i++) {
			this.coefficients[i].copy(coefficients[i]);
		}

		return this;
	}

	zero() {
		for (let i = 0; i < 9; i++) {
			this.coefficients[i].set(0, 0, 0);
		}

		return this;
	}

	// get the radiance in the direction of the normal
	// target is a Vector3
	getAt(normal, target) {
		// normal is assumed to be unit length

		const x = normal.x,
			y = normal.y,
			z = normal.z;

		const coeff = this.coefficients;

		// band 0
		target.copy(coeff[0]).multiplyByScalar(0.282095);

		// band 1
		target.addScaledVector(coeff[1], 0.488603 * y);
		target.addScaledVector(coeff[2], 0.488603 * z);
		target.addScaledVector(coeff[3], 0.488603 * x);

		// band 2
		target.addScaledVector(coeff[4], 1.092548 * (x * y));
		target.addScaledVector(coeff[5], 1.092548 * (y * z));
		target.addScaledVector(coeff[6], 0.315392 * (3.0 * z * z - 1.0));
		target.addScaledVector(coeff[7], 1.092548 * (x * z));
		target.addScaledVector(coeff[8], 0.546274 * (x * x - y * y));

		return target;
	}

	// get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
	// target is a Vector3
	// https://graphics.stanford.edu/papers/envmap/envmap.pdf
	getIrradianceAt(normal, target) {
		// normal is assumed to be unit length

		const x = normal.x,
			y = normal.y,
			z = normal.z;

		const coeff = this.coefficients;

		// band 0
		target.copy(coeff[0]).multiplyByScalar(0.886227); // π * 0.282095

		// band 1
		target.addScaledVector(coeff[1], 2.0 * 0.511664 * y); // ( 2 * π / 3 ) * 0.488603
		target.addScaledVector(coeff[2], 2.0 * 0.511664 * z);
		target.addScaledVector(coeff[3], 2.0 * 0.511664 * x);

		// band 2
		target.addScaledVector(coeff[4], 2.0 * 0.429043 * x * y); // ( π / 4 ) * 1.092548
		target.addScaledVector(coeff[5], 2.0 * 0.429043 * y * z);
		target.addScaledVector(coeff[6], 0.743125 * z * z - 0.247708); // ( π / 4 ) * 0.315392 * 3
		target.addScaledVector(coeff[7], 2.0 * 0.429043 * x * z);
		target.addScaledVector(coeff[8], 0.429043 * (x * x - y * y)); // ( π / 4 ) * 0.546274

		return target;
	}

	add(sh) {
		for (let i = 0; i < 9; i++) {
			this.coefficients[i].add(sh.coefficients[i]);
		}

		return this;
	}

	addScaledSH(sh, s) {
		for (let i = 0; i < 9; i++) {
			this.coefficients[i].addScaledVector(sh.coefficients[i], s);
		}

		return this;
	}

	scale(s) {
		for (let i = 0; i < 9; i++) {
			this.coefficients[i].multiplyByScalar(s);
		}

		return this;
	}

	lerp(sh, alpha) {
		for (let i = 0; i < 9; i++) {
			this.coefficients[i].lerp(sh.coefficients[i], alpha);
		}

		return this;
	}

	equals(sh) {
		for (let i = 0; i < 9; i++) {
			if (!this.coefficients[i].equals(sh.coefficients[i])) {
				return false;
			}
		}

		return true;
	}

	copy(sh) {
		return this.set(sh.coefficients);
	}

	clone() {
		return new SphericalHarmonics3().copy(this);
	}

	fromArray(array, offset = 0) {
		const coefficients = this.coefficients;

		for (let i = 0; i < 9; i++) {
			const index = offset + i * 3;
			coefficients[i].set(array[index], array[index + 1], array[index + 2]);
		}

		return this;
	}

	toArray(array = [], offset = 0) {
		const coefficients = this.coefficients;

		for (let i = 0; i < 9; i++) {
			const index = offset + i * 3;
			array[index] = coefficients[i].x;
			array[index + 1] = coefficients[i].y;
			array[index + 2] = coefficients[i].z;
			// coefficients[i].toArray(array, offset + (i * 3));
		}

		return array;
	}

	// evaluate the basis functions
	// shBasis is an Array[ 9 ]
	static getBasisAt(normal, shBasis) {
		// normal is assumed to be unit length

		const x = normal.x,
			y = normal.y,
			z = normal.z;

		// band 0
		shBasis[0] = 0.282095;

		// band 1
		shBasis[1] = 0.488603 * y;
		shBasis[2] = 0.488603 * z;
		shBasis[3] = 0.488603 * x;

		// band 2
		shBasis[4] = 1.092548 * x * y;
		shBasis[5] = 1.092548 * y * z;
		shBasis[6] = 0.315392 * (3 * z * z - 1);
		shBasis[7] = 1.092548 * x * z;
		shBasis[8] = 0.546274 * (x * x - y * y);
	}
}
